using Gtk;
using System;
using System.IO;
using System.IO.Ports;
using System.Configuration;
using System.Configuration.Install;

namespace uploader
{
	public delegate void LogEventHandler (string message,int level = 0);

	public delegate void ProgressEventHandler (double completed);
	
	public delegate void MonitorEventHandler (string portname);

	public delegate void UploadEventHandler (string portname,string filename);

	public delegate void QuitEventHandler ();

	class MainClass
	{
		public static void Main (string[] args)
		{
			Application.Init ();	// XXX requires Gtk#
			
			new AppLogic ();
			
			Application.Run ();		// XXX requires Gtk#
		}	
	}
	
	class AppLogic
	{
		MainWindow	win;
		Mon			mon;
		IHex		ihex;
		Uploader	upl;
		SerialPort	port;
		bool		upload_in_progress;
		int			logLevel = 0;	// Adjust for more logging output
		string								config_name = "SiKUploader";
		System.Configuration.Configuration	config;
		ConfigSection 						config_section;

		public AppLogic ()
		{
			// Get the current configuration file.
			config = ConfigurationManager.OpenExeConfiguration (ConfigurationUserLevel.None);
		
			// Look for our settings and add/create them if missing
			if (config.Sections [config_name] == null) {
				config_section = new ConfigSection ();
				config.Sections.Add (config_name, config_section);
				config_section.SectionInformation.ForceSave = true;
				config.Save (ConfigurationSaveMode.Full);
			}
			config_section = config.GetSection (config_name) as ConfigSection;

			// Hook up main window events
			win = new MainWindow ();
			win.MonitorEvent += show_monitor;
			win.UploadEvent += do_upload;
			win.LogEvent += log;
			win.QuitEvent += at_exit;
			
			// restore the last path that we uploaded
			win.FileName = config_section.lastPath;
			
			// restore the last port that we opened
			win.PortName = config_section.lastPort;
			
			// Create the intelhex loader
			ihex = new IHex ();
			ihex.LogEvent += log;
			
			// And the uploader
			upl = new Uploader ();
			upl.LogEvent += log;
			upl.ProgressEvent += win.set_progress;
			
			// Emit some basic help
			log ("Select a serial port and a .hex file to be uploaded, then hit Upload.\n");
			
			win.Show ();
		}
		
		private void at_exit ()
		{
			log ("saving configuration");
			config.Save (ConfigurationSaveMode.Modified);
		}
		
		private void set_port (string portname)
		{
			if (upload_in_progress)
				throw new Exception ("attempt to change port while upload in progress");
			
			// If the port name hasn't changed, keep the existing port
			if ((port != null) && (portname.Equals (port.PortName)))
				return;
			
			// dispose of the old port
			if (mon != null)
				mon.disconnect ();
			if ((port != null) && port.IsOpen)
				port.Close ();
			port = null;
			
			try {
				// create a new one
				port = new SerialPort (portname, 115200, Parity.None, 8, StopBits.One);

				log ("opening " + port.PortName + "\n", 1);
				port.Open ();
				log ("opened " + port.PortName + "\n", 1);
				
				// remember that we successfully opened this port
				config_section.lastPort = port.PortName;
			} catch {
				log ("FAIL: cannot open " + port.PortName + "\n");
				port = null;
			}
			
			// if we have a port and a monitor, connect it to the new port
			if ((port != null) && (mon != null))
				mon.connect (port);
		}
		
		/// <summary>
		/// Create a serial port monitor for the supplied port name, in response
		/// to an event generated by the main window.
		/// </summary>
		/// <param name='portname'>
		/// Name of the serial port to open.
		/// </param>
		private void show_monitor (string portname)
		{
			// ignore monitor activation requests while uploading
			if (upload_in_progress)
				return;
			
			// set/switch port assignment
			set_port (portname);
			
			// create the monitor if it's not already open
			if (mon == null) {
				
				// create a new monitor
				mon = new Mon (port);
				//mon.QuitEvent += end_monitor; ??
				mon.DeleteEvent += end_monitor;
				mon.LogEvent += log;
				
				// and connect it to the port
				mon.connect (port);
				
			} else {
				
				// bring the window to the front
				mon.Present ();
			}
		}
		
		/// <summary>
		/// Called when the monitor window is closed.
		/// </summary>
		/// <param name='obj'>
		/// Object.
		/// </param>
		/// <param name='args'>
		/// Arguments.
		/// </param>
		private void end_monitor (object sender, DeleteEventArgs args)
		{
			// forget about the monitor
			mon = null;
		}
		
		private void do_upload (string portname, string filename)
		{
			// ignore duplicate upload events
			if (upload_in_progress)
				return;
			
			// disconnect the monitor so that we can connect the uploader
			if (mon != null)
				mon.disconnect ();
			
			// set/switch serial port
			set_port (portname);
			
			upload_in_progress = true;
			try {
				try_upload (filename);
				
				// remember that we successfully uploaded this path
				config_section.lastPath = filename;
				
			} catch (Exception e) {
				log ("upload failed: " + e.Message, 1);
				log (e.StackTrace, 1);
				// don't log here, better errors should be logged by lower levels
			}
			upload_in_progress = false;
			win.set_progress (0);
			
			// if the monitor is still up, reconnect it, possibly to the
			// device that was just uploaded to
			if (mon != null) {
				mon.connect (port);
			} else {
				// dispose of the port so that other tools can talk to the device
				port.Close ();
				port = null;
			}
		}
		
		private void try_upload (string filename)
		{
			// load the intelhex file
			ihex.load (filename);
		
			// and try to upload it
			upl.upload (port, ihex);
		}
		
		private void log (string message, int level = 0)
		{
			// log a message to the console
			if (level <= logLevel)
				Console.Write (message);
		
			// level 0 messages go to the status bar
			if (level == 0) {
				
				win.set_status (message);
			}
		}
	}
	
	public sealed class ConfigSection : ConfigurationSection
	{
		public ConfigSection ()
		{
		}

		[ConfigurationProperty("lastPath", DefaultValue = "")]
		public string lastPath {
			get {
				return (string)this ["lastPath"];
			}
			set {
				this ["lastPath"] = value;
			}
		}
	
		[ConfigurationProperty("lastPort", DefaultValue = "")]
		public string lastPort {
			get {
				return (string)this ["lastPort"];
			}
			set {
				this ["lastPort"] = value;
			}		
		}
	}

}
